﻿using System;
using System.IO;
using System.Linq;
using Microsoft.Research.Kinect.Audio;
using Microsoft.Speech.AudioFormat;
using Microsoft.Speech.Recognition;

using System.Threading;

namespace InteractiveKidsShow
{
    class Speech
    {
        private const string RecognizerId = "SR_MS_en-US_Kinect_10.0";

        public delegate void SpeechResponseEventHandler(object sender, SpeechResponseArgs e);
        public event SpeechResponseEventHandler SpeechResponse;
        
        SynchronizationContext m_context;
        private KinectAudioSource source;
        private RecognizerInfo ri;
        private SpeechRecognitionEngine sre;
        private Stream s;
        Choices words;
        GrammarBuilder gb;
        Grammar g;

        Boolean needSpeechAnswer = false;
        String wordRequested = "";
        DateTime timeRequested = DateTime.MinValue;
        int secondsToWaitForAnswer;

        //This is the method called when a speech input is required.
        //The word required is passed in as a string.
        //The amount of time, in sedconds, to wait for an answer is passed in as an integer.
        public void requestSpeech(String wordRequested, int secondsToWaitForAnswer)
        {
            this.wordRequested = wordRequested;
            this.secondsToWaitForAnswer = secondsToWaitForAnswer;
            timeRequested = DateTime.Now;
            needSpeechAnswer = true;
        }

        public Speech(SynchronizationContext m)
        {
            m_context = m;
        }

        public void InitAudio()
        {
            source = new KinectAudioSource();

            source.FeatureMode = true;
            source.AutomaticGainControl = false; //Important to turn this off for speech recognition
            source.SystemMode = SystemMode.OptibeamArrayOnly; //No AEC for this sample

            ri = SpeechRecognitionEngine.InstalledRecognizers().Where(r => r.Id == RecognizerId).FirstOrDefault();

            if (ri == null)
            {
                return;
            }

            sre = new SpeechRecognitionEngine(ri.Id);

            words = new Choices();

            //Reads in a list of words from a text file. One word per line.
            try
            {
                using (StreamReader sr = new StreamReader("242demo.Grammar"))
                {
                    String line;
                    // Read in lines from the text file to build the grammar.
                    while ((line = sr.ReadLine()) != null)
                    {
                        words.Add(line);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            //Add some basic words just in case text file is not read in.
            words.Add("Yes");
            words.Add("No");
            words.Add("Map");

            gb = new GrammarBuilder();
            //Specify the culture to match the recognizer in case we are running in a different culture.                                 
            gb.Culture = ri.Culture;
            gb.Append(words);

            // Create the actual Grammar instance, and then load it into the speech recognizer.
            g = new Grammar(gb);

            //Load the Grammar into the speech recognizer.
            sre.LoadGrammar(g);
            sre.SpeechRecognized += SreSpeechRecognized;
            sre.SpeechHypothesized += SreSpeechHypothesized;
            sre.SpeechRecognitionRejected += SreSpeechRecognitionRejected;

            s = source.Start();

            sre.SetInputToAudioStream(s, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));

            sre.RecognizeAsync(RecognizeMode.Multiple);
        }
        //This method is called if the speech is too far from any known words.
        private void SreSpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            Console.WriteLine("\nSpeech Rejected");
            if (e.Result != null)
                DumpRecordedAudio(e.Result.Audio);
        }
        //This method is called when a word is detected to give an approximation of the word the recognizer believes was said.
        private void SreSpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
            if (DateTime.Now.Subtract(timeRequested).Seconds > secondsToWaitForAnswer)
            {
                needSpeechAnswer = false;
                timeRequested = DateTime.MinValue;
            }

            Console.Write("\rSpeech Hypothesized: \t{0}", e.Result.Text);
        }
        //This method is called when a word has been accepted as a known word.
        private void SreSpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            Console.WriteLine("\nSpeech Recognized: \t{0}", e.Result.Text);

            if (needSpeechAnswer)//Only continue if speech input is needed at this point.
            {
                if (e.Result.Text == wordRequested)//Check if the word said was the word requested.
                {
                    //Stop searching for speech input. 
                    //This is very important as otherwise two events can be fired too quickly causing problems in any listeners.
                    needSpeechAnswer = false;
                    m_context.Send(
                        delegate
                        {
                            SpeechResponseArgs args = new SpeechResponseArgs(true, e.Result.Text); //The arguments for SpeechResponse are created.
                            SpeechResponse(this, args); //A SpeechResponse event is generated.
                        }
                        , null
                    );
                }
            }
        }
        //DumpRecordedAudio is called whenever a word is rejected for being unknown.
        //A sound file is created containing 'audio', the recording of the rejected sound piece.

        private static void DumpRecordedAudio(RecognizedAudio audio)
        {
            if (audio == null) return;//If no audio is recorded return.

            int fileId = 0;
            string filename;
            while (File.Exists((filename = "RetainedAudio_" + fileId + ".wav")))//Find the next free nunmber slot.
                fileId++;

            Console.WriteLine("\nWriting file: {0}", filename);//Informs the user an audio dump is taking place.
            using (var file = new FileStream(filename, System.IO.FileMode.CreateNew))
                audio.WriteToWaveStream(file);//Writes the audio to the file.
        }

    }
}